<?php
/**
 * User: zhuyajie
 * Date: 12-11-8
 * Time: 下午8:12
 * @property mixed inaction
 */
class DebugStartBehavior extends Behavior
{
	protected $options = array(
		'CONSOLE_LABEL_ON'        => true, );
	public static $action;

	/**
	 * 执行行为 run方法是Behavior唯一的接口
	 * @access public
	 *
	 * @param mixed $params  行为参数
	 *
	 * @return void
	 */
	public function run( &$params ) {
		if ( C( 'CONSOLE_LABEL_ON' ) ) {
			if ( self::$action!=ACTION_NAME ) {
				self::$action = ACTION_NAME;
				Console::start(MODULE_NAME.'Action'. '::'.ACTION_NAME."()" );
			}
		}
	}
}

class Console
{
	public static $handleErrors=true;
	public static $handleExceptions=true;
	public static $sourceBasePath=null;
	/**
	 * @var bool 在代码中可以动态关闭调试器
	 */
	public static $on=true;

	static function debug() {
		if ( APP_DEBUG==false || C('CONSOLE_ON')==false ||self::$on==false){
			return;
		}
		//监测类库包，函数包
		$fp = class_exists( 'FirePHP' );
		$fb = class_exists( 'FB' );
		$cp = class_exists( 'ChromePhp' );
		$pc = class_exists( 'PhpConsole' );
		//分配参数
		$args  = func_get_args();
		$count = count( $args );
		if ( $count==0 ) {
			return;
		} elseif ( $count==1 ) {
			$var = func_get_arg( 0 );
		} elseif ( $count==2 ) {
			$var   = func_get_arg( 0 );
			$label = func_get_arg( 1 );
		} else {
			$var     = func_get_arg( 0 );
			$label   = func_get_arg( 1 );
			$seurity = func_get_arg( 2 );
		}
		ob_start();
		if ( $pc ) {
			PhpConsole::start(self::$handleErrors, self::$handleExceptions, self::$sourceBasePath);
		}
		if ( $fp && $fb ) {
			switch ( $count ) {
				case 1:
					FB::send( $var );
					break;
				case 2:
					FB::send( $var, $label );
					break;
				case 3:
					FB::send( $var, $label, strtoupper( $seurity ) );
			}
		}
		if ( $cp ) {
			switch ( $count ) {
				case 1:
					ChromePhp::log( $var );
					break;
				case 2:
					ChromePhp::log( $label, $var );
					break;
				case 3:
					if ( $var!='END' ) {
						ChromePhp::log( $label, $var, strtolower( $seurity ) );
					}
			}
		}
	}

	static function warn( $var, $label = 'WARNING' ) {
		self::debug( $var, $label, 'WARN' );
	}

	static function error( $var, $label = 'ERROR  ' ) {
		self::debug( $var, $label, 'ERROR' );
	}

	static function log( $var, $label = 'LOG    ' ) {
		self::debug( $var, $label, 'LOG' );
	}

	static function info( $var, $label = 'INFO   ' ) {
		self::debug( $var, $label, 'INFO' );
	}

	static function start( $name = 'GROUP', $options = null ) {
		if ( APP_DEBUG==false || C('CONSOLE_ON')==false || self::$on==false) {
			return;
		}
		self::debug( $name, null, 'group' );
		FB::group( $name, (array)$options );
	}

	static function end() {
		self::debug( 'END', null, 'GROUP_END' );
	}
}

/**
 * Copyright 2012 Craig Campbell
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Server Side Chrome PHP debugger class
 *
 * @package ChromePhp
 * @author Craig Campbell <iamcraigcampbell@gmail.com>
 */
class ChromePhp
{
	/**
	 * @var string
	 */
	const VERSION = '3.0';

	/**
	 * @var string
	 */
	const HEADER_NAME = 'X-ChromePhp-Data';

	/**
	 * @var string
	 */
	const BACKTRACE_LEVEL = 'backtrace_level';

	/**
	 * @var string
	 */
	const LOG = 'log';

	/**
	 * @var string
	 */
	const WARN = 'warn';

	/**
	 * @var string
	 */
	const ERROR = 'error';

	/**
	 * @var string
	 */
	const GROUP = 'group';

	/**
	 * @var string
	 */
	const INFO = 'info';

	/**
	 * @var string
	 */
	const GROUP_END = 'GROUP_END';

	/**
	 * @var string
	 */
	const GROUP_COLLAPSED = 'groupCollapsed';

	/**
	 * @var string
	 */
	protected $_php_version;

	/**
	 * @var int
	 */
	protected $_timestamp;

	/**
	 * @var array
	 */
	protected $_json = array(
		'version' => self::VERSION,
		'columns' => array('label', 'log', 'backtrace', 'type'),
		'rows' => array()
	);

	/**
	 * @var array
	 */
	protected $_backtraces = array();

	/**
	 * @var bool
	 */
	protected $_error_triggered = false;

	/**
	 * @var array
	 */
	protected $_settings = array(
		self::BACKTRACE_LEVEL => 1
	);

	/**
	 * @var ChromePhp
	 */
	protected static $_instance;

	/**
	 * Prevent recursion when working with objects referring to each other
	 *
	 * @var array
	 */
	protected $_processed = array();

	/**
	 * constructor
	 */
	private function __construct()
	{
		$this->_php_version = phpversion();
		$this->_timestamp = $this->_php_version >= 5.1 ? $_SERVER['REQUEST_TIME'] : time();
		$this->_json['request_uri'] = $_SERVER['REQUEST_URI'];
	}

	/**
	 * gets instance of this class
	 *
	 * @return ChromePhp
	 */
	public static function getInstance()
	{
		if (self::$_instance === null) {
			self::$_instance = new ChromePhp();
		}
		return self::$_instance;
	}

	/**
	 * logs a variable to the console
	 *
	 * @param string label
	 * @param mixed value
	 * @param string severity ChromePhp::LOG || ChromePhp::WARN || ChromePhp::ERROR
	 */
	public static function log()
	{
		$args = func_get_args();
		$severity = count($args) == 3 ? array_pop($args) : '';

		// save precious bytes
		if ($severity == self::LOG) {
			$severity = '';
		}

		return self::_log($args + array('type' => $severity));
	}

	/**
	 * logs a warning to the console
	 *
	 * @param string label
	 * @param mixed value
	 */
	public static function warn()
	{
		return self::_log(func_get_args() + array('type' => self::WARN));
	}

	/**
	 * logs an error to the console
	 *
	 * @param string label
	 * @param mixed value
	 * @return void
	 */
	public static function error()
	{
		return self::_log(func_get_args() + array('type' => self::ERROR));
	}

	/**
	 * sends a group log
	 *
	 * @param string value
	 */
	public static function group()
	{
		return self::_log(func_get_args() + array('type' => self::GROUP));
	}

	/**
	 * sends an info log
	 *
	 * @param string value
	 */
	public static function info()
	{
		return self::_log(func_get_args() + array('type' => self::INFO));
	}

	/**
	 * sends a collapsed group log
	 *
	 * @param string value
	 */
	public static function groupCollapsed()
	{
		return self::_log(func_get_args() + array('type' => self::GROUP_COLLAPSED));
	}

	/**
	 * ends a group log
	 *
	 * @param string value
	 */
	public static function groupEnd()
	{
		return self::_log(func_get_args() + array('type' => self::GROUP_END));
	}

	/**
	 * internal logging call
	 *
	 * @param string $type
	 */
	protected static function _log(array $args)
	{
		$type = $args['type'];
		unset($args['type']);

		// nothing passed in, don't do anything
		if (count($args) == 0 && $type != self::GROUP_END) {
			return;
		}

		// default to single
		$label = null;
		$value = isset($args[0]) ? $args[0] : '';

		$logger = self::getInstance();

		// if there are two values passed in then the first one is the label
		if (count($args) == 2) {
			$label = $args[0].':';
			$value = $args[1];
		}

		$logger->_processed = array();
		$value = $logger->_convert($value);

		$backtrace = debug_backtrace(false);
		$level = $logger->getSetting(self::BACKTRACE_LEVEL);

		$backtrace_message = 'unknown';
		if (isset($backtrace[$level]['file']) && isset($backtrace[$level]['line'])) {
			$backtrace_message = $backtrace[$level]['file'] . ' : ' . $backtrace[$level]['line'];
		}

		$logger->_addRow($label, $value, $backtrace_message, $type);
	}

	/**
	 * converts an object to a better format for logging
	 *
	 * @param Object
	 * @return array
	 */
	protected function _convert($object)
	{
		// if this isn't an object then just return it
		if (!is_object($object)) {
			return $object;
		}

		//Mark this object as processed so we don't convert it twice and it
		//Also avoid recursion when objects refer to each other
		$this->_processed[] = $object;

		$object_as_array = array();

		// first add the class name
		$object_as_array['___class_name'] = get_class($object);

		// loop through object vars
		$object_vars = get_object_vars($object);
		foreach ($object_vars as $key => $value) {

			// same instance as parent object
			if ($value === $object || in_array($value, $this->_processed, true)) {
				$value = 'recursion - parent object [' . get_class($value) . ']';
			}
			$object_as_array[$key] = $this->_convert($value);
		}

		$reflection = new ReflectionClass($object);

		// loop through the properties and add those
		foreach ($reflection->getProperties() as $property) {

			// if one of these properties was already added above then ignore it
			if (array_key_exists($property->getName(), $object_vars)) {
				continue;
			}
			$type = $this->_getPropertyKey($property);

			if ($this->_php_version >= 5.3) {
				$property->setAccessible(true);
			}

			try {
				$value = $property->getValue($object);
			} catch (ReflectionException $e) {
				$value = 'only PHP 5.3 can access private/protected properties';
			}

			// same instance as parent object
			if ($value === $object || in_array($value, $this->_processed, true)) {
				$value = 'recursion - parent object [' . get_class($value) . ']';
			}

			$object_as_array[$type] = $this->_convert($value);
		}
		return $object_as_array;
	}

	/**
	 * takes a reflection property and returns a nicely formatted key of the property name
	 *
	 * @param ReflectionProperty
	 * @return string
	 */
	protected function _getPropertyKey(ReflectionProperty $property)
	{
		$static = $property->isStatic() ? ' static' : '';
		if ($property->isPublic()) {
			return 'public' . $static . ' ' . $property->getName();
		}

		if ($property->isProtected()) {
			return 'protected' . $static . ' ' . $property->getName();
		}

		if ($property->isPrivate()) {
			return 'private' . $static . ' ' . $property->getName();
		}
	}

	/**
	 * adds a value to the data array
	 *
	 * @var mixed
	 * @return void
	 */
	protected function _addRow($label, $log, $backtrace, $type)
	{
		// if this is logged on the same line for example in a loop, set it to null to save space
		if (in_array($backtrace, $this->_backtraces)) {
			$backtrace = null;
		}

		if ($backtrace !== null) {
			$this->_backtraces[] = $backtrace;
		}

		$row = array($label, $log, $backtrace, $type);

		$this->_json['rows'][] = $row;
		$this->_writeHeader($this->_json);
	}

	protected function _writeHeader($data)
	{
		header(self::HEADER_NAME . ': ' . $this->_encode($data));
	}

	/**
	 * encodes the data to be sent along with the request
	 *
	 * @param array $data
	 * @return string
	 */
	protected function _encode($data)
	{
		return base64_encode(utf8_encode(json_encode($data)));
	}

	/**
	 * adds a setting
	 *
	 * @param string key
	 * @param mixed value
	 * @return void
	 */
	public function addSetting($key, $value)
	{
		$this->_settings[$key] = $value;
	}

	/**
	 * add ability to set multiple settings in one call
	 *
	 * @param array $settings
	 * @return void
	 */
	public function addSettings(array $settings)
	{
		foreach ($settings as $key => $value) {
			$this->addSetting($key, $value);
		}
	}

	/**
	 * gets a setting
	 *
	 * @param string key
	 * @return mixed
	 */
	public function getSetting($key)
	{
		if (!isset($this->_settings[$key])) {
			return null;
		}
		return $this->_settings[$key];
	}
}

/**
 *
 * *** BEGIN LICENSE BLOCK *****
 * This file is part of FirePHP (http://www.firephp.org/).
 * Software License Agreement (New BSD License)
 * Copyright (c) 2006-2010, Christoph Dorn
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *     * Neither the name of Christoph Dorn nor the names of its
 *       contributors may be used to endorse or promote products derived from this
 *       software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ***** END LICENSE BLOCK *****
 * @copyright       Copyright (C) 2007-2009 Christoph Dorn
 * @author          Christoph Dorn <christoph@christophdorn.com>
 * @license         http://www.opensource.org/licenses/bsd-license.php
 * @package         FirePHPCore
 */
/**
 * @see http://code.google.com/p/firephp/issues/detail?id=112
 */
if ( !defined( 'E_STRICT' ) ) {
	define('E_STRICT', 2048);
}
if ( !defined( 'E_RECOVERABLE_ERROR' ) ) {
	define('E_RECOVERABLE_ERROR', 4096);
}
if ( !defined( 'E_DEPRECATED' ) ) {
	define('E_DEPRECATED', 8192);
}
if ( !defined( 'E_USER_DEPRECATED' ) ) {
	define('E_USER_DEPRECATED', 16384);
}
/**
 * Sends the given data to the FirePHP Firefox Extension.
 * The data can be displayed in the Firebug Console or in the
 * "Server" request tab.
 * For more information see: http://www.firephp.org/
 * @copyright       Copyright (C) 2007-2009 Christoph Dorn
 * @author          Christoph Dorn <christoph@christophdorn.com>
 * @license         http://www.opensource.org/licenses/bsd-license.php
 * @package         FirePHPCore
 */
class FirePHP
{

	/**
	 * FirePHP version
	 * @var string
	 */
	const VERSION = '0.3'; // @pinf replace '0.3' with '%%package.version%%'
	/**
	 * Firebug LOG level
	 * Logs a message to firebug console.
	 * @var string
	 */
	const LOG = 'LOG';

	/**
	 * Firebug INFO level
	 * Logs a message to firebug console and displays an info icon before the message.
	 * @var string
	 */
	const INFO = 'INFO';

	/**
	 * Firebug WARN level
	 * Logs a message to firebug console, displays an warning icon before the message and colors the line turquoise.
	 * @var string
	 */
	const WARN = 'WARN';

	/**
	 * Firebug ERROR level
	 * Logs a message to firebug console, displays an error icon before the message and colors the line yellow. Also increments the firebug error count.
	 * @var string
	 */
	const ERROR = 'ERROR';

	/**
	 * Dumps a variable to firebug's server panel
	 * @var string
	 */
	const DUMP = 'DUMP';

	/**
	 * Displays a stack trace in firebug console
	 * @var string
	 */
	const TRACE = 'TRACE';

	/**
	 * Displays an exception in firebug console
	 * Increments the firebug error count.
	 * @var string
	 */
	const EXCEPTION = 'EXCEPTION';

	/**
	 * Displays an table in firebug console
	 * @var string
	 */
	const TABLE = 'TABLE';

	/**
	 * Starts a group in firebug console
	 * @var string
	 */
	const GROUP_START = 'GROUP_START';

	/**
	 * Ends a group in firebug console
	 * @var string
	 */
	const GROUP_END = 'GROUP_END';

	/**
	 * Singleton instance of FirePHP
	 * @var FirePHP
	 */
	protected static $instance = null;

	/**
	 * Flag whether we are logging from within the exception handler
	 * @var boolean
	 */
	protected $inExceptionHandler = false;

	/**
	 * Flag whether to throw PHP errors that have been converted to ErrorExceptions
	 * @var boolean
	 */
	protected $throwErrorExceptions = true;

	/**
	 * Flag whether to convert PHP assertion errors to Exceptions
	 * @var boolean
	 */
	protected $convertAssertionErrorsToExceptions = true;

	/**
	 * Flag whether to throw PHP assertion errors that have been converted to Exceptions
	 * @var boolean
	 */
	protected $throwAssertionExceptions = false;

	/**
	 * Wildfire protocol message index
	 * @var int
	 */
	protected $messageIndex = 1;

	/**
	 * Options for the library
	 * @var array
	 */
	protected $options = array(
		'maxDepth'            => 10,
		'maxObjectDepth'      => 5,
		'maxArrayDepth'       => 5,
		'useNativeJsonEncode' => true,
		'includeLineNumbers'  => true );

	/**
	 * Filters used to exclude object members when encoding
	 * @var array
	 */
	protected $objectFilters = array(
		'firephp'            => array(
			'objectStack',
			'instance',
			'json_objectStack' ),
		'firephp_test_class' => array(
			'objectStack',
			'instance',
			'json_objectStack' ) );

	/**
	 * A stack of objects used to detect recursion during object encoding
	 * @var object
	 */
	protected $objectStack = array();

	/**
	 * Flag to enable/disable logging
	 * @var boolean
	 */
	protected $enabled = true;

	/**
	 * The insight console to log to if applicable
	 * @var object
	 */
	protected $logToInsightConsole = null;

	/**
	 * When the object gets serialized only include specific object members.
	 * @return array
	 */
	public function __sleep() {
		return array(
			'options',
			'objectFilters',
			'enabled' );
	}

	/**
	 * Gets singleton instance of FirePHP
	 *
	 * @param boolean $AutoCreate
	 *
	 * @return FirePHP
	 */
	public static function getInstance( $AutoCreate = false ) {
		if ( $AutoCreate===true && !self::$instance ) {
			self::init();
		}
		return self::$instance;
	}

	/**
	 * Creates FirePHP object and stores it for singleton access
	 * @return FirePHP
	 */
	public static function init() {
		return self::setInstance( new self() );
	}

	/**
	 * Set the instance of the FirePHP singleton
	 *
	 * @param FirePHP $instance The FirePHP object instance
	 *
	 * @return FirePHP
	 */
	public static function setInstance( $instance ) {
		return self::$instance = $instance;
	}

	/**
	 * Set an Insight console to direct all logging calls to
	 *
	 * @param object $console The console object to log to
	 *
	 * @return void
	 */
	public function setLogToInsightConsole( $console ) {
		if ( is_string( $console ) ) {
			if ( get_class( $this )!='FirePHP_Insight' && !is_subclass_of( $this, 'FirePHP_Insight' ) ) {
				throw new Exception('FirePHP instance not an instance or subclass of FirePHP_Insight!');
			}
			$this->logToInsightConsole = $this->to( 'request' )->console( $console );
		} else {
			$this->logToInsightConsole = $console;
		}
	}

	/**
	 * Enable and disable logging to Firebug
	 *
	 * @param boolean $Enabled TRUE to enable, FALSE to disable
	 *
	 * @return void
	 */
	public function setEnabled( $Enabled ) {
		$this->enabled = $Enabled;
	}

	/**
	 * Check if logging is enabled
	 * @return boolean TRUE if enabled
	 */
	public function getEnabled() {
		return $this->enabled;
	}

	/**
	 * Specify a filter to be used when encoding an object
	 * Filters are used to exclude object members.
	 *
	 * @param string $Class  The class name of the object
	 * @param array  $Filter An array of members to exclude
	 *
	 * @return void
	 */
	public function setObjectFilter( $Class, $Filter ) {
		$this->objectFilters[strtolower( $Class )] = $Filter;
	}

	/**
	 * Set some options for the library
	 * Options:
	 *  - maxDepth: The maximum depth to traverse (default: 10)
	 *  - maxObjectDepth: The maximum depth to traverse objects (default: 5)
	 *  - maxArrayDepth: The maximum depth to traverse arrays (default: 5)
	 *  - useNativeJsonEncode: If true will use json_encode() (default: true)
	 *  - includeLineNumbers: If true will include line numbers and filenames (default: true)
	 *
	 * @param array $Options The options to be set
	 *
	 * @return void
	 */
	public function setOptions( $Options ) {
		$this->options = array_merge( $this->options, $Options );
	}

	/**
	 * Get options from the library
	 * @return array The currently set options
	 */
	public function getOptions() {
		return $this->options;
	}

	/**
	 * Set an option for the library
	 *
	 * @param string $Name
	 * @param mixed  $Value
	 *
	 * @throws Exception
	 * @return void
	 */
	public function setOption( $Name, $Value ) {
		if ( !isset($this->options[$Name]) ) {
			throw $this->newException( 'Unknown option: '.$Name );
		}
		$this->options[$Name] = $Value;
	}

	/**
	 * Get an option from the library
	 *
	 * @param string $Name
	 *
	 * @throws Exception
	 * @return mixed
	 */
	public function getOption( $Name ) {
		if ( !isset($this->options[$Name]) ) {
			throw $this->newException( 'Unknown option: '.$Name );
		}
		return $this->options[$Name];
	}

	/**
	 * Register FirePHP as your error handler
	 * Will throw exceptions for each php error.
	 * @return mixed Returns a string containing the previously defined error handler (if any)
	 */
	public function registerErrorHandler( $throwErrorExceptions = false ) {
		//NOTE: The following errors will not be caught by this error handler:
		//      E_ERROR, E_PARSE, E_CORE_ERROR,
		//      E_CORE_WARNING, E_COMPILE_ERROR,
		//      E_COMPILE_WARNING, E_STRICT
		$this->throwErrorExceptions = $throwErrorExceptions;
		return set_error_handler( array(
									   $this,
									   'errorHandler' ) );
	}

	/**
	 * FirePHP's error handler
	 * Throws exception for each php error that will occur.
	 *
	 * @param int    $errno
	 * @param string $errstr
	 * @param string $errfile
	 * @param int    $errline
	 * @param array  $errcontext
	 */
	public function errorHandler( $errno, $errstr, $errfile, $errline, $errcontext ) {
		// Don't throw exception if error reporting is switched off
		if ( error_reporting()==0 ) {
			return;
		}
		// Only throw exceptions for errors we are asking for
		if ( error_reporting()&$errno ) {
			$exception = new ErrorException($errstr, 0, $errno, $errfile, $errline);
			if ( $this->throwErrorExceptions ) {
				throw $exception;
			} else {
				$this->fb( $exception );
			}
		}
	}

	/**
	 * Register FirePHP as your exception handler
	 * @return mixed Returns the name of the previously defined exception handler,
	 *               or NULL on error.
	 *               If no previous handler was defined, NULL is also returned.
	 */
	public function registerExceptionHandler() {
		return set_exception_handler( array(
										   $this,
										   'exceptionHandler' ) );
	}

	/**
	 * FirePHP's exception handler
	 * Logs all exceptions to your firebug console and then stops the script.
	 *
	 * @param Exception $Exception
	 *
	 * @throws Exception
	 */
	function exceptionHandler( $Exception ) {
		$this->inExceptionHandler = true;
		header( 'HTTP/1.1 500 Internal Server Error' );
		try {
			$this->fb( $Exception );
		} catch ( Exception $e ) {
			echo 'We had an exception: '.$e;
		}
		$this->inExceptionHandler = false;
	}

	/**
	 * Register FirePHP driver as your assert callback
	 *
	 * @param boolean $convertAssertionErrorsToExceptions
	 * @param boolean $throwAssertionExceptions
	 *
	 * @return mixed Returns the original setting or FALSE on errors
	 */
	public function registerAssertionHandler( $convertAssertionErrorsToExceptions = true, $throwAssertionExceptions = false ) {
		$this->convertAssertionErrorsToExceptions = $convertAssertionErrorsToExceptions;
		$this->throwAssertionExceptions           = $throwAssertionExceptions;
		if ( $throwAssertionExceptions && !$convertAssertionErrorsToExceptions ) {
			throw $this->newException( 'Cannot throw assertion exceptions as assertion errors are not being converted to exceptions!' );
		}
		return assert_options( ASSERT_CALLBACK, array(
													 $this,
													 'assertionHandler' ) );
	}

	/**
	 * FirePHP's assertion handler
	 * Logs all assertions to your firebug console and then stops the script.
	 *
	 * @param string $file File source of assertion
	 * @param int    $line Line source of assertion
	 * @param mixed  $code Assertion code
	 */
	public function assertionHandler( $file, $line, $code ) {
		if ( $this->convertAssertionErrorsToExceptions ) {
			$exception = new ErrorException('Assertion Failed - Code[ '.$code.' ]', 0, null, $file, $line);
			if ( $this->throwAssertionExceptions ) {
				throw $exception;
			} else {
				$this->fb( $exception );
			}
		} else {
			$this->fb( $code, 'Assertion Failed', FirePHP::ERROR, array(
																	   'File'  => $file,
																	   'Line'  => $line ) );
		}
	}

	/**
	 * Start a group for following messages.
	 * Options:
	 *   Collapsed: [true|false]
	 *   Color:     [#RRGGBB|ColorName]
	 *
	 * @param string $Name
	 * @param array  $Options OPTIONAL Instructions on how to log the group
	 *
	 * @return true
	 * @throws Exception
	 */
	public function group( $Name, $Options = null ) {
		if ( !$Name ) {
			throw $this->newException( 'You must specify a label for the group!' );
		}
		if ( $Options ) {
			if ( !is_array( $Options ) ) {
				throw $this->newException( 'Options must be defined as an array!' );
			}
			if ( array_key_exists( 'Collapsed', $Options ) ) {
				$Options['Collapsed'] = ($Options['Collapsed']) ? 'true' : 'false';
			}
		}
		return $this->fb( null, $Name, FirePHP::GROUP_START, $Options );
	}

	/**
	 * Ends a group you have started before
	 * @return true
	 * @throws Exception
	 */
	public function groupEnd() {
		return $this->fb( null, null, FirePHP::GROUP_END );
	}

	/**
	 * Log object with label to firebug console
	 * @see FirePHP::LOG
	 *
	 * @param mixes  $Object
	 * @param string $Label
	 *
	 * @return true
	 * @throws Exception
	 */
	public function log( $Object, $Label = null, $Options = array() ) {
		return $this->fb( $Object, $Label, FirePHP::LOG, $Options );
	}

	/**
	 * Log object with label to firebug console
	 * @see FirePHP::INFO
	 *
	 * @param mixes  $Object
	 * @param string $Label
	 *
	 * @return true
	 * @throws Exception
	 */
	public function info( $Object, $Label = null, $Options = array() ) {
		return $this->fb( $Object, $Label, FirePHP::INFO, $Options );
	}

	/**
	 * Log object with label to firebug console
	 * @see FirePHP::WARN
	 *
	 * @param mixes  $Object
	 * @param string $Label
	 *
	 * @return true
	 * @throws Exception
	 */
	public function warn( $Object, $Label = null, $Options = array() ) {
		return $this->fb( $Object, $Label, FirePHP::WARN, $Options );
	}

	/**
	 * Log object with label to firebug console
	 * @see FirePHP::ERROR
	 *
	 * @param mixes  $Object
	 * @param string $Label
	 *
	 * @return true
	 * @throws Exception
	 */
	public function error( $Object, $Label = null, $Options = array() ) {
		return $this->fb( $Object, $Label, FirePHP::ERROR, $Options );
	}

	/**
	 * Dumps key and variable to firebug server panel
	 * @see FirePHP::DUMP
	 *
	 * @param string $Key
	 * @param mixed  $Variable
	 *
	 * @return true
	 * @throws Exception
	 */
	public function dump( $Key, $Variable, $Options = array() ) {
		if ( !is_string( $Key ) ) {
			throw $this->newException( 'Key passed to dump() is not a string' );
		}
		if ( strlen( $Key )>100 ) {
			throw $this->newException( 'Key passed to dump() is longer than 100 characters' );
		}
		if ( !preg_match_all( '/^[a-zA-Z0-9-_\.:]*$/', $Key, $m ) ) {
			throw $this->newException( 'Key passed to dump() contains invalid characters [a-zA-Z0-9-_\.:]' );
		}
		return $this->fb( $Variable, $Key, FirePHP::DUMP, $Options );
	}

	/**
	 * Log a trace in the firebug console
	 * @see FirePHP::TRACE
	 *
	 * @param string $Label
	 *
	 * @return true
	 * @throws Exception
	 */
	public function trace( $Label ) {
		return $this->fb( $Label, FirePHP::TRACE );
	}

	/**
	 * Log a table in the firebug console
	 * @see FirePHP::TABLE
	 *
	 * @param string $Label
	 * @param string $Table
	 *
	 * @return true
	 * @throws Exception
	 */
	public function table( $Label, $Table, $Options = array() ) {
		return $this->fb( $Table, $Label, FirePHP::TABLE, $Options );
	}

	/**
	 * Insight API wrapper
	 * @see Insight_Helper::to()
	 */
	public static function to() {
		$instance = self::getInstance();
		if ( !method_exists( $instance, "_to" ) ) {
			throw new Exception("FirePHP::to() implementation not loaded");
		}
		$args = func_get_args();
		return call_user_func_array( array(
										  $instance,
										  '_to' ), $args );
	}

	/**
	 * Insight API wrapper
	 * @see Insight_Helper::plugin()
	 */
	public static function plugin() {
		$instance = self::getInstance();
		if ( !method_exists( $instance, "_plugin" ) ) {
			throw new Exception("FirePHP::plugin() implementation not loaded");
		}
		$args = func_get_args();
		return call_user_func_array( array(
										  $instance,
										  '_plugin' ), $args );
	}

	/**
	 * Check if FirePHP is installed on client
	 * @return boolean
	 */
	public function detectClientExtension() {
		// Check if FirePHP is installed on client via User-Agent header
		if ( @preg_match_all( '/\sFirePHP\/([\.\d]*)\s?/si', $this->getUserAgent(), $m ) && version_compare( $m[1][0], '0.0.6', '>=' ) ) {
			return true;
		} else // Check if FirePHP is installed on client via X-FirePHP-Version header
		{
			if ( @preg_match_all( '/^([\.\d]*)$/si', $this->getRequestHeader( "X-FirePHP-Version" ), $m ) && version_compare( $m[1][0], '0.0.6', '>=' ) ) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Log varible to Firebug
	 * @see http://www.firephp.org/Wiki/Reference/Fb
	 *
	 * @param mixed $Object The variable to be logged
	 *
	 * @return true Return TRUE if message was added to headers, FALSE otherwise
	 * @throws Exception
	 */
	public function fb( $Object ) {
		if ( $this instanceof FirePHP_Insight && method_exists( $this, '_logUpgradeClientMessage' ) ) {
			if ( !FirePHP_Insight::$upgradeClientMessageLogged ) { // avoid infinite recursion as _logUpgradeClientMessage() logs a message
				$this->_logUpgradeClientMessage();
			}
		}
		static $insightGroupStack = array();
		if ( !$this->getEnabled() ) {
			return false;
		}
		if ( $this->headersSent( $filename, $linenum ) ) {
			// If we are logging from within the exception handler we cannot throw another exception
			if ( $this->inExceptionHandler ) {
				// Simply echo the error out to the page
				echo '<div style="border: 2px solid red; font-family: Arial; font-size: 12px; background-color: lightgray; padding: 5px;"><span style="color: red; font-weight: bold;">FirePHP ERROR:</span> Headers already sent in <b>'.$filename.'</b> on line <b>'.$linenum.'</b>. Cannot send log data to FirePHP. You must have Output Buffering enabled via ob_start() or output_buffering ini directive.</div>';
			} else {
				throw $this->newException( 'Headers already sent in '.$filename.' on line '.$linenum.'. Cannot send log data to FirePHP. You must have Output Buffering enabled via ob_start() or output_buffering ini directive.' );
			}
		}
		$Type    = null;
		$Label   = null;
		$Options = array();
		if ( func_num_args()==1 ) {
		} else {
			if ( func_num_args()==2 ) {
				switch ( func_get_arg( 1 ) ) {
					case self::LOG:
					case self::INFO:
					case self::WARN:
					case self::ERROR:
					case self::DUMP:
					case self::TRACE:
					case self::EXCEPTION:
					case self::TABLE:
					case self::GROUP_START:
					case self::GROUP_END:
						$Type = func_get_arg( 1 );
						break;
					default:
						$Label = func_get_arg( 1 );
						break;
				}
			} else {
				if ( func_num_args()==3 ) {
					$Type  = func_get_arg( 2 );
					$Label = func_get_arg( 1 );
				} else {
					if ( func_num_args()==4 ) {
						$Type    = func_get_arg( 2 );
						$Label   = func_get_arg( 1 );
						$Options = func_get_arg( 3 );
					} else {
						throw $this->newException( 'Wrong number of arguments to fb() function!' );
					}
				}
			}
		}
		if ( $this->logToInsightConsole!==null && (get_class( $this )=='FirePHP_Insight' || is_subclass_of( $this, 'FirePHP_Insight' )) ) {
			$msg = $this->logToInsightConsole;
			if ( $Object instanceof Exception ) {
				$Type = self::EXCEPTION;
			}
			if ( $Label && $Type!=self::TABLE && $Type!=self::GROUP_START ) {
				$msg = $msg->label( $Label );
			}
			switch ( $Type ) {
				case self::DUMP:
				case self::LOG:
					return $msg->log( $Object );
				case self::INFO:
					return $msg->info( $Object );
				case self::WARN:
					return $msg->warn( $Object );
				case self::ERROR:
					return $msg->error( $Object );
				case self::TRACE:
					return $msg->trace( $Object );
				case self::EXCEPTION:
					return $this->plugin( 'engine' )->handleException( $Object, $msg );
				case self::TABLE:
					if ( isset($Object[0]) && !is_string( $Object[0] ) && $Label ) {
						$Object = array(
							$Label,
							$Object );
					}
					return $msg->table( $Object[0], array_slice( $Object[1], 1 ), $Object[1][0] );
				case self::GROUP_START:
					$insightGroupStack[] = $msg->group( md5( $Label ) )->open();
					return $msg->log( $Label );
				case self::GROUP_END:
					if ( count( $insightGroupStack )==0 ) {
						throw new Error('Too many groupEnd() as opposed to group() calls!');
					}
					$group = array_pop( $insightGroupStack );
					return $group->close();
				default:
					return $msg->log( $Object );
			}
		}
		if ( !$this->detectClientExtension() ) {
			return false;
		}
		$meta                  = array();
		$skipFinalObjectEncode = false;
		if ( $Object instanceof Exception ) {
			$meta['file'] = $this->_escapeTraceFile( $Object->getFile() );
			$meta['line'] = $Object->getLine();
			$trace = $Object->getTrace();
			if ( $Object instanceof ErrorException && isset($trace[0]['function']) && $trace[0]['function']=='errorHandler' && isset($trace[0]['class']) && $trace[0]['class']=='FirePHP' ) {
				$severity = false;
				switch ( $Object->getSeverity() ) {
					case E_WARNING:
						$severity = 'E_WARNING';
						break;
					case E_NOTICE:
						$severity = 'E_NOTICE';
						break;
					case E_USER_ERROR:
						$severity = 'E_USER_ERROR';
						break;
					case E_USER_WARNING:
						$severity = 'E_USER_WARNING';
						break;
					case E_USER_NOTICE:
						$severity = 'E_USER_NOTICE';
						break;
					case E_STRICT:
						$severity = 'E_STRICT';
						break;
					case E_RECOVERABLE_ERROR:
						$severity = 'E_RECOVERABLE_ERROR';
						break;
					case E_DEPRECATED:
						$severity = 'E_DEPRECATED';
						break;
					case E_USER_DEPRECATED:
						$severity = 'E_USER_DEPRECATED';
						break;
				}
				$Object                = array(
					'Class'  => get_class( $Object ),
					'Message'=> $severity.': '.$Object->getMessage(),
					'File'   => $this->_escapeTraceFile( $Object->getFile() ),
					'Line'   => $Object->getLine(),
					'Type'   => 'trigger',
					'Trace'  => $this->_escapeTrace( array_splice( $trace, 2 ) ) );
				$skipFinalObjectEncode = true;
			} else {
				$Object                = array(
					'Class'  => get_class( $Object ),
					'Message'=> $Object->getMessage(),
					'File'   => $this->_escapeTraceFile( $Object->getFile() ),
					'Line'   => $Object->getLine(),
					'Type'   => 'throw',
					'Trace'  => $this->_escapeTrace( $trace ) );
				$skipFinalObjectEncode = true;
			}
			$Type = self::EXCEPTION;
		} else {
			if ( $Type==self::TRACE ) {
				$trace = debug_backtrace();
				if ( !$trace ) {
					return false;
				}
				for ( $i = 0; $i<sizeof( $trace ); $i++ ) {
					if ( isset($trace[$i]['class']) && isset($trace[$i]['file']) && ($trace[$i]['class']=='FirePHP' || $trace[$i]['class']=='FB') && (substr( $this->_standardizePath( $trace[$i]['file'] ), -18, 18 )=='FirePHPCore/fb.php' || substr( $this->_standardizePath( $trace[$i]['file'] ), -29, 29 )=='FirePHPCore/FirePHP.class.php') ) {
						/* Skip - FB::trace(), FB::send(), $firephp->trace(), $firephp->fb() */
					} else {
						if ( isset($trace[$i]['class']) && isset($trace[$i+1]['file']) && $trace[$i]['class']=='FirePHP' && substr( $this->_standardizePath( $trace[$i+1]['file'] ), -18, 18 )=='FirePHPCore/fb.php' ) {
							/* Skip fb() */
						} else {
							if ( $trace[$i]['function']=='fb' || $trace[$i]['function']=='trace' || $trace[$i]['function']=='send' ) {
								$Object                = array(
									'Class'   => isset($trace[$i]['class']) ? $trace[$i]['class'] : '',
									'Type'    => isset($trace[$i]['type']) ? $trace[$i]['type'] : '',
									'Function'=> isset($trace[$i]['function']) ? $trace[$i]['function'] : '',
									'Message' => $trace[$i]['args'][0],
									'File'    => isset($trace[$i]['file']) ? $this->_escapeTraceFile( $trace[$i]['file'] ) : '',
									'Line'    => isset($trace[$i]['line']) ? $trace[$i]['line'] : '',
									'Args'    => isset($trace[$i]['args']) ? $this->encodeObject( $trace[$i]['args'] ) : '',
									'Trace'   => $this->_escapeTrace( array_splice( $trace, $i+1 ) ) );
								$skipFinalObjectEncode = true;
								$meta['file']          = isset($trace[$i]['file']) ? $this->_escapeTraceFile( $trace[$i]['file'] ) : '';
								$meta['line']          = isset($trace[$i]['line']) ? $trace[$i]['line'] : '';
								break;
							}
						}
					}
				}
			} else {
				if ( $Type==self::TABLE ) {
					if ( isset($Object[0]) && is_string( $Object[0] ) ) {
						$Object[1] = $this->encodeTable( $Object[1] );
					} else {
						$Object = $this->encodeTable( $Object );
					}
					$skipFinalObjectEncode = true;
				} else {
					if ( $Type==self::GROUP_START ) {
						if ( !$Label ) {
							throw $this->newException( 'You must specify a label for the group!' );
						}
					} else {
						if ( $Type===null ) {
							$Type = self::LOG;
						}
					}
				}
			}
		}
		if ( $this->options['includeLineNumbers'] ) {
			if ( !isset($meta['file']) || !isset($meta['line']) ) {
				$trace = debug_backtrace();
				for ( $i = 0; $trace && $i<sizeof( $trace ); $i++ ) {
					if ( isset($trace[$i]['class']) && isset($trace[$i]['file']) && ($trace[$i]['class']=='FirePHP' || $trace[$i]['class']=='FB') && (substr( $this->_standardizePath( $trace[$i]['file'] ), -18, 18 )=='FirePHPCore/fb.php' || substr( $this->_standardizePath( $trace[$i]['file'] ), -29, 29 )=='FirePHPCore/FirePHP.class.php') ) {
						/* Skip - FB::trace(), FB::send(), $firephp->trace(), $firephp->fb() */
					} else if ( isset($trace[$i]['class']) && isset($trace[$i+1]['file']) && $trace[$i]['class']=='FirePHP' && substr( $this->_standardizePath( $trace[$i+1]['file'] ), -18, 18 )=='FirePHPCore/fb.php' ) {
						/* Skip fb() */
					} else {
						if ( isset($trace[$i]['file']) && substr( $this->_standardizePath( $trace[$i]['file'] ), -18, 18 )=='FirePHPCore/fb.php' ) {
							/* Skip FB::fb() */
						} else {
							$meta['file'] = isset($trace[$i]['file']) ? $this->_escapeTraceFile( $trace[$i]['file'] ) : '';
							$meta['line'] = isset($trace[$i]['line']) ? $trace[$i]['line'] : '';
							break;
						}
					}
				}
			}
		} else {
			unset($meta['file']);
			unset($meta['line']);
		}
		$this->setHeader( 'X-Wf-Protocol-1', 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2' );
		$this->setHeader( 'X-Wf-1-Plugin-1', 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/'.self::VERSION );
		$structure_index = 1;
		if ( $Type==self::DUMP ) {
			$structure_index = 2;
			$this->setHeader( 'X-Wf-1-Structure-2', 'http://meta.firephp.org/Wildfire/Structure/FirePHP/Dump/0.1' );
		} else {
			$this->setHeader( 'X-Wf-1-Structure-1', 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1' );
		}
		if ( $Type==self::DUMP ) {
			$msg = '{"'.$Label.'":'.$this->jsonEncode( $Object, $skipFinalObjectEncode ).'}';
		} else {
			$msg_meta         = $Options;
			$msg_meta['Type'] = $Type;
			if ( $Label!==null ) {
				$msg_meta['Label'] = $Label;
			}
			if ( isset($meta['file']) && !isset($msg_meta['File']) ) {
				$msg_meta['File'] = $meta['file'];
			}
			if ( isset($meta['line']) && !isset($msg_meta['Line']) ) {
				$msg_meta['Line'] = $meta['line'];
			}
			$msg = '['.$this->jsonEncode( $msg_meta ).','.$this->jsonEncode( $Object, $skipFinalObjectEncode ).']';
		}
		$parts = explode( "\n", chunk_split( $msg, 5000, "\n" ) );
		for ( $i = 0; $i<count( $parts ); $i++ ) {
			$part = $parts[$i];
			if ( $part ) {
				if ( count( $parts )>2 ) {
					// Message needs to be split into multiple parts
					$this->setHeader( 'X-Wf-1-'.$structure_index.'-'.'1-'.$this->messageIndex, (($i==0) ? strlen( $msg ) : '').'|'.$part.'|'.(($i<count( $parts )-2) ? '\\' : '') );
				} else {
					$this->setHeader( 'X-Wf-1-'.$structure_index.'-'.'1-'.$this->messageIndex, strlen( $part ).'|'.$part.'|' );
				}
				$this->messageIndex++;
				if ( $this->messageIndex>99999 ) {
					throw $this->newException( 'Maximum number (99,999) of messages reached!' );
				}
			}
		}
		$this->setHeader( 'X-Wf-1-Index', $this->messageIndex-1 );
		return true;
	}

	/**
	 * Standardizes path for windows systems.
	 *
	 * @param string $Path
	 *
	 * @return string
	 */
	protected function _standardizePath( $Path ) {
		return preg_replace( '/\\\\+/', '/', $Path );
	}

	/**
	 * Escape trace path for windows systems
	 *
	 * @param array $Trace
	 *
	 * @return array
	 */
	protected function _escapeTrace( $Trace ) {
		if ( !$Trace )
			return $Trace;
		for ( $i = 0; $i<sizeof( $Trace ); $i++ ) {
			if ( isset($Trace[$i]['file']) ) {
				$Trace[$i]['file'] = $this->_escapeTraceFile( $Trace[$i]['file'] );
			}
			if ( isset($Trace[$i]['args']) ) {
				$Trace[$i]['args'] = $this->encodeObject( $Trace[$i]['args'] );
			}
		}
		return $Trace;
	}

	/**
	 * Escape file information of trace for windows systems
	 *
	 * @param string $File
	 *
	 * @return string
	 */
	protected function _escapeTraceFile( $File ) {
		/* Check if we have a windows filepath */
		if ( strpos( $File, '\\' ) ) {
			/* First strip down to single \ */
			$file = preg_replace( '/\\\\+/', '\\', $File );
			return $file;
		}
		return $File;
	}

	/**
	 * Check if headers have already been sent
	 *
	 * @param string  $Filename
	 * @param integer $Linenum
	 */
	protected function headersSent( &$Filename, &$Linenum ) {
		return headers_sent( $Filename, $Linenum );
	}

	/**
	 * Send header
	 *
	 * @param string $Name
	 * @param string $Value
	 */
	protected function setHeader( $Name, $Value ) {
		header( $Name.': '.$Value );
	}

	/**
	 * Get user agent
	 * @return string|false
	 */
	protected function getUserAgent() {
		if ( !isset($_SERVER['HTTP_USER_AGENT']) )
			return false;
		return $_SERVER['HTTP_USER_AGENT'];
	}

	/**
	 * Get all request headers
	 * @return array
	 */
	public static function getAllRequestHeaders() {
		static $_cached_headers = false;
		if ( $_cached_headers!==false ) {
			return $_cached_headers;
		}
		$headers = array();
		if ( function_exists( 'getallheaders' ) ) {
			foreach ( getallheaders() as $name => $value ) {
				$headers[strtolower( $name )] = $value;
			}
		} else {
			foreach ( $_SERVER as $name => $value ) {
				if ( substr( $name, 0, 5 )=='HTTP_' ) {
					$headers[strtolower( str_replace( ' ', '-', str_replace( '_', ' ', substr( $name, 5 ) ) ) )] = $value;
				}
			}
		}
		return $_cached_headers = $headers;
	}

	/**
	 * Get a request header
	 * @return string|false
	 */
	protected function getRequestHeader( $Name ) {
		$headers = self::getAllRequestHeaders();
		if ( isset($headers[strtolower( $Name )]) ) {
			return $headers[strtolower( $Name )];
		}
		return false;
	}

	/**
	 * Returns a new exception
	 *
	 * @param string $Message
	 *
	 * @return Exception
	 */
	protected function newException( $Message ) {
		return new Exception($Message);
	}

	/**
	 * Encode an object into a JSON string
	 * Uses PHP's jeson_encode() if available
	 *
	 * @param object $Object The object to be encoded
	 *
	 * @return string The JSON string
	 */
	public function jsonEncode( $Object, $skipObjectEncode = false ) {
		if ( !$skipObjectEncode ) {
			$Object = $this->encodeObject( $Object );
		}
		if ( function_exists( 'json_encode' ) && $this->options['useNativeJsonEncode']!=false ) {
			return json_encode( $Object );
		} else {
			return $this->json_encode( $Object );
		}
	}

	/**
	 * Encodes a table by encoding each row and column with encodeObject()
	 *
	 * @param array $Table The table to be encoded
	 *
	 * @return array
	 */
	protected function encodeTable( $Table ) {
		if ( !$Table )
			return $Table;
		$new_table = array();
		foreach ( $Table as $row ) {
			if ( is_array( $row ) ) {
				$new_row = array();
				foreach ( $row as $item ) {
					$new_row[] = $this->encodeObject( $item );
				}
				$new_table[] = $new_row;
			}
		}
		return $new_table;
	}

	/**
	 * Encodes an object including members with
	 * protected and private visibility
	 *
	 * @param Object $Object The object to be encoded
	 * @param int    $Depth  The current traversal depth
	 *
	 * @return array All members of the object
	 */
	protected function encodeObject( $Object, $ObjectDepth = 1, $ArrayDepth = 1, $MaxDepth = 1 ) {
		if ( $MaxDepth>$this->options['maxDepth'] ) {
			return '** Max Depth ('.$this->options['maxDepth'].') **';
		}
		$return = array();
		if ( is_resource( $Object ) ) {
			return '** '.(string)$Object.' **';
		} else if ( is_object( $Object ) ) {
			if ( $ObjectDepth>$this->options['maxObjectDepth'] ) {
				return '** Max Object Depth ('.$this->options['maxObjectDepth'].') **';
			}
			foreach ( $this->objectStack as $refVal ) {
				if ( $refVal===$Object ) {
					return '** Recursion ('.get_class( $Object ).') **';
				}
			}
			array_push( $this->objectStack, $Object );
			$return['__className'] = $class = get_class( $Object );
			$class_lower           = strtolower( $class );
			$reflectionClass = new ReflectionClass($class);
			$properties      = array();
			foreach ( $reflectionClass->getProperties() as $property ) {
				$properties[$property->getName()] = $property;
			}
			$members = (array)$Object;
			foreach ( $properties as $plain_name => $property ) {
				$name = $raw_name = $plain_name;
				if ( $property->isStatic() ) {
					$name = 'static:'.$name;
				}
				if ( $property->isPublic() ) {
					$name = 'public:'.$name;
				} else if ( $property->isPrivate() ) {
					$name     = 'private:'.$name;
					$raw_name = "\0".$class."\0".$raw_name;
				} else if ( $property->isProtected() ) {
					$name     = 'protected:'.$name;
					$raw_name = "\0".'*'."\0".$raw_name;
				}
				if ( !(isset($this->objectFilters[$class_lower]) && is_array( $this->objectFilters[$class_lower] ) && in_array( $plain_name, $this->objectFilters[$class_lower] )) ) {
					if ( array_key_exists( $raw_name, $members ) && !$property->isStatic() ) {
						$return[$name] = $this->encodeObject( $members[$raw_name], $ObjectDepth+1, 1, $MaxDepth+1 );
					} else {
						if ( method_exists( $property, 'setAccessible' ) ) {
							$property->setAccessible( true );
							$return[$name] = $this->encodeObject( $property->getValue( $Object ), $ObjectDepth+1, 1, $MaxDepth+1 );
						} else if ( $property->isPublic() ) {
							$return[$name] = $this->encodeObject( $property->getValue( $Object ), $ObjectDepth+1, 1, $MaxDepth+1 );
						} else {
							$return[$name] = '** Need PHP 5.3 to get value **';
						}
					}
				} else {
					$return[$name] = '** Excluded by Filter **';
				}
			}
			// Include all members that are not defined in the class
			// but exist in the object
			foreach ( $members as $raw_name => $value ) {
				$name = $raw_name;
				if ( $name{0}=="\0" ) {
					$parts = explode( "\0", $name );
					$name  = $parts[2];
				}
				$plain_name = $name;
				if ( !isset($properties[$name]) ) {
					$name = 'undeclared:'.$name;
					if ( !(isset($this->objectFilters[$class_lower]) && is_array( $this->objectFilters[$class_lower] ) && in_array( $plain_name, $this->objectFilters[$class_lower] )) ) {
						$return[$name] = $this->encodeObject( $value, $ObjectDepth+1, 1, $MaxDepth+1 );
					} else {
						$return[$name] = '** Excluded by Filter **';
					}
				}
			}
			array_pop( $this->objectStack );
		} elseif ( is_array( $Object ) ) {
			if ( $ArrayDepth>$this->options['maxArrayDepth'] ) {
				return '** Max Array Depth ('.$this->options['maxArrayDepth'].') **';
			}
			foreach ( $Object as $key => $val ) {
				// Encoding the $GLOBALS PHP array causes an infinite loop
				// if the recursion is not reset here as it contains
				// a reference to itself. This is the only way I have come up
				// with to stop infinite recursion in this case.
				if ( $key=='GLOBALS' && is_array( $val ) && array_key_exists( 'GLOBALS', $val ) ) {
					$val['GLOBALS'] = '** Recursion (GLOBALS) **';
				}
				$return[$key] = $this->encodeObject( $val, 1, $ArrayDepth+1, $MaxDepth+1 );
			}
		} else {
			if ( self::is_utf8( $Object ) ) {
				return $Object;
			} else {
				return utf8_encode( $Object );
			}
		}
		return $return;
	}

	/**
	 * Returns true if $string is valid UTF-8 and false otherwise.
	 *
	 * @param mixed $str String to be tested
	 *
	 * @return boolean
	 */
	protected static function is_utf8( $str ) {
		if ( function_exists( 'mb_detect_encoding' ) ) {
			return (mb_detect_encoding( $str )=='UTF-8');
		}
		$c    = 0;
		$b    = 0;
		$bits = 0;
		$len  = strlen( $str );
		for ( $i = 0; $i<$len; $i++ ) {
			$c = ord( $str[$i] );
			if ( $c>128 ) {
				if ( ($c>=254) )
					return false; elseif ( $c>=252 )
					$bits = 6; elseif ( $c>=248 )
					$bits = 5; elseif ( $c>=240 )
					$bits = 4; elseif ( $c>=224 )
					$bits = 3; elseif ( $c>=192 )
					$bits = 2; else return false;
				if ( ($i+$bits)>$len )
					return false;
				while ( $bits>1 ) {
					$i++;
					$b = ord( $str[$i] );
					if ( $b<128 || $b>191 )
						return false;
					$bits--;
				}
			}
		}
		return true;
	}

	/**
	 * Converts to and from JSON format.
	 * JSON (JavaScript Object Notation) is a lightweight data-interchange
	 * format. It is easy for humans to read and write. It is easy for machines
	 * to parse and generate. It is based on a subset of the JavaScript
	 * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
	 * This feature can also be found in  Python. JSON is a text format that is
	 * completely language independent but uses conventions that are familiar
	 * to programmers of the C-family of languages, including C, C++, C#, Java,
	 * JavaScript, Perl, TCL, and many others. These properties make JSON an
	 * ideal data-interchange language.
	 * This package provides a simple encoder and decoder for JSON notation. It
	 * is intended for use with client-side Javascript applications that make
	 * use of HTTPRequest to perform server communication functions - data can
	 * be encoded into JSON notation for use in a client-side javascript, or
	 * decoded from incoming Javascript requests. JSON format is native to
	 * Javascript, and can be directly eval()'ed with no further parsing
	 * overhead
	 * All strings should be in ASCII or UTF-8 format!
	 * LICENSE: Redistribution and use in source and binary forms, with or
	 * without modification, are permitted provided that the following
	 * conditions are met: Redistributions of source code must retain the
	 * above copyright notice, this list of conditions and the following
	 * disclaimer. Redistributions in binary form must reproduce the above
	 * copyright notice, this list of conditions and the following disclaimer
	 * in the documentation and/or other materials provided with the
	 * distribution.
	 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
	 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
	 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
	 * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
	 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
	 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
	 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
	 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
	 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
	 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
	 * DAMAGE.
	 * @category
	 * @package     Services_JSON
	 * @author      Michal Migurski <mike-json@teczno.com>
	 * @author      Matt Knapp <mdknapp[at]gmail[dot]com>
	 * @author      Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
	 * @author      Christoph Dorn <christoph@christophdorn.com>
	 * @copyright   2005 Michal Migurski
	 * @version     CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
	 * @license     http://www.opensource.org/licenses/bsd-license.php
	 * @link        http://pear.php.net/pepr/pepr-proposal-show.php?id=198
	 */
	/**
	 * Keep a list of objects as we descend into the array so we can detect recursion.
	 */
	private $json_objectStack = array();

	/**
	 * convert a string from one UTF-8 char to one UTF-16 char
	 * Normally should be handled by mb_convert_encoding, but
	 * provides a slower PHP-only method for installations
	 * that lack the multibye string extension.
	 *
	 * @param    string  $utf8   UTF-8 character
	 *
	 * @return   string  UTF-16 character
	 * @access   private
	 */
	private function json_utf82utf16( $utf8 ) {
		// oh please oh please oh please oh please oh please
		if ( function_exists( 'mb_convert_encoding' ) ) {
			return mb_convert_encoding( $utf8, 'UTF-16', 'UTF-8' );
		}
		switch ( strlen( $utf8 ) ) {
			case 1:
				// this case should never be reached, because we are in ASCII range
				// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
				return $utf8;
			case 2:
				// return a UTF-16 character from a 2-byte UTF-8 char
				// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
				return chr( 0x07&(ord( $utf8{0} ) >> 2) ).chr( (0xC0&(ord( $utf8{0} ) << 6))|(0x3F&ord( $utf8{1} )) );
			case 3:
				// return a UTF-16 character from a 3-byte UTF-8 char
				// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
				return chr( (0xF0&(ord( $utf8{0} ) << 4))|(0x0F&(ord( $utf8{1} ) >> 2)) ).chr( (0xC0&(ord( $utf8{1} ) << 6))|(0x7F&ord( $utf8{2} )) );
		}
		// ignoring UTF-32 for now, sorry
		return '';
	}

	/**
	 * encodes an arbitrary variable into JSON format
	 *
	 * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
	 *                           see argument 1 to Services_JSON() above for array-parsing behavior.
	 *                           if var is a strng, note that encode() always expects it
	 *                           to be in ASCII or UTF-8 format!
	 *
	 * @return   mixed   JSON string representation of input var or an error if a problem occurs
	 * @access   public
	 */
	private function json_encode( $var ) {
		if ( is_object( $var ) ) {
			if ( in_array( $var, $this->json_objectStack ) ) {
				return '"** Recursion **"';
			}
		}
		switch ( gettype( $var ) ) {
			case 'boolean':
				return $var ? 'true' : 'false';
			case 'NULL':
				return 'null';
			case 'integer':
				return (int)$var;
			case 'double':
			case 'float':
				return (float)$var;
			case 'string':
				// STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
				$ascii      = '';
				$strlen_var = strlen( $var );
				/*
                * Iterate over every character in the string,
                * escaping with a slash or encoding to UTF-8 where necessary
                */
				for ( $c = 0; $c<$strlen_var; ++$c ) {
					$ord_var_c = ord( $var{$c} );
					switch ( true ) {
						case $ord_var_c==0x08:
							$ascii .= '\b';
							break;
						case $ord_var_c==0x09:
							$ascii .= '\t';
							break;
						case $ord_var_c==0x0A:
							$ascii .= '\n';
							break;
						case $ord_var_c==0x0C:
							$ascii .= '\f';
							break;
						case $ord_var_c==0x0D:
							$ascii .= '\r';
							break;
						case $ord_var_c==0x22:
						case $ord_var_c==0x2F:
						case $ord_var_c==0x5C:
							// double quote, slash, slosh
							$ascii .= '\\'.$var{$c};
							break;
						case (($ord_var_c>=0x20) && ($ord_var_c<=0x7F)):
							// characters U-00000000 - U-0000007F (same as ASCII)
							$ascii .= $var{$c};
							break;
						case (($ord_var_c&0xE0)==0xC0):
							// characters U-00000080 - U-000007FF, mask 110XXXXX
							// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
							$char = pack( 'C*', $ord_var_c, ord( $var{$c+1} ) );
							$c += 1;
							$utf16 = $this->json_utf82utf16( $char );
							$ascii .= sprintf( '\u%04s', bin2hex( $utf16 ) );
							break;
						case (($ord_var_c&0xF0)==0xE0):
							// characters U-00000800 - U-0000FFFF, mask 1110XXXX
							// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
							$char = pack( 'C*', $ord_var_c, ord( $var{$c+1} ), ord( $var{$c+2} ) );
							$c += 2;
							$utf16 = $this->json_utf82utf16( $char );
							$ascii .= sprintf( '\u%04s', bin2hex( $utf16 ) );
							break;
						case (($ord_var_c&0xF8)==0xF0):
							// characters U-00010000 - U-001FFFFF, mask 11110XXX
							// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
							$char = pack( 'C*', $ord_var_c, ord( $var{$c+1} ), ord( $var{$c+2} ), ord( $var{$c+3} ) );
							$c += 3;
							$utf16 = $this->json_utf82utf16( $char );
							$ascii .= sprintf( '\u%04s', bin2hex( $utf16 ) );
							break;
						case (($ord_var_c&0xFC)==0xF8):
							// characters U-00200000 - U-03FFFFFF, mask 111110XX
							// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
							$char = pack( 'C*', $ord_var_c, ord( $var{$c+1} ), ord( $var{$c+2} ), ord( $var{$c+3} ), ord( $var{$c+4} ) );
							$c += 4;
							$utf16 = $this->json_utf82utf16( $char );
							$ascii .= sprintf( '\u%04s', bin2hex( $utf16 ) );
							break;
						case (($ord_var_c&0xFE)==0xFC):
							// characters U-04000000 - U-7FFFFFFF, mask 1111110X
							// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
							$char = pack( 'C*', $ord_var_c, ord( $var{$c+1} ), ord( $var{$c+2} ), ord( $var{$c+3} ), ord( $var{$c+4} ), ord( $var{$c+5} ) );
							$c += 5;
							$utf16 = $this->json_utf82utf16( $char );
							$ascii .= sprintf( '\u%04s', bin2hex( $utf16 ) );
							break;
					}
				}
				return '"'.$ascii.'"';
			case 'array':
				/*
                 * As per JSON spec if any array key is not an integer
                 * we must treat the the whole array as an object. We
                 * also try to catch a sparsely populated associative
                 * array with numeric keys here because some JS engines
                 * will create an array with empty indexes up to
                 * max_index which can cause memory issues and because
                 * the keys, which may be relevant, will be remapped
                 * otherwise.
                 *
                 * As per the ECMA and JSON specification an object may
                 * have any string as a property. Unfortunately due to
                 * a hole in the ECMA specification if the key is a
                 * ECMA reserved word or starts with a digit the
                 * parameter is only accessible using ECMAScript's
                 * bracket notation.
                 */
				// treat as a JSON object
				if ( is_array( $var ) && count( $var ) && (array_keys( $var )!==range( 0, sizeof( $var )-1 )) ) {
					$this->json_objectStack[] = $var;
					$properties = array_map( array(
												  $this,
												  'json_name_value' ), array_keys( $var ), array_values( $var ) );
					array_pop( $this->json_objectStack );
					foreach ( $properties as $property ) {
						if ( $property instanceof Exception ) {
							return $property;
						}
					}
					return '{'.join( ',', $properties ).'}';
				}
				$this->json_objectStack[] = $var;
				// treat it like a regular array
				$elements = array_map( array(
											$this,
											'json_encode' ), $var );
				array_pop( $this->json_objectStack );
				foreach ( $elements as $element ) {
					if ( $element instanceof Exception ) {
						return $element;
					}
				}
				return '['.join( ',', $elements ).']';
			case 'object':
				$vars = self::encodeObject( $var );
				$this->json_objectStack[] = $var;
				$properties = array_map( array(
											  $this,
											  'json_name_value' ), array_keys( $vars ), array_values( $vars ) );
				array_pop( $this->json_objectStack );
				foreach ( $properties as $property ) {
					if ( $property instanceof Exception ) {
						return $property;
					}
				}
				return '{'.join( ',', $properties ).'}';
			default:
				return null;
		}
	}

	/**
	 * array-walking function for use in generating JSON-formatted name-value pairs
	 *
	 * @param    string  $name   name of key to use
	 * @param    mixed   $value  reference to an array element to be encoded
	 *
	 * @return   string  JSON-formatted name-value pair, like '"name":value'
	 * @access   private
	 */
	private function json_name_value( $name, $value ) {
		// Encoding the $GLOBALS PHP array causes an infinite loop
		// if the recursion is not reset here as it contains
		// a reference to itself. This is the only way I have come up
		// with to stop infinite recursion in this case.
		if ( $name=='GLOBALS' && is_array( $value ) && array_key_exists( 'GLOBALS', $value ) ) {
			$value['GLOBALS'] = '** Recursion **';
		}
		$encoded_value = $this->json_encode( $value );
		if ( $encoded_value instanceof Exception ) {
			return $encoded_value;
		}
		return $this->json_encode( strval( $name ) ).':'.$encoded_value;
	}

	/**
	 * @deprecated
	 */
	public function setProcessorUrl( $URL ) {
		trigger_error( "The FirePHP::setProcessorUrl() method is no longer supported", E_USER_DEPRECATED );
	}

	/**
	 * @deprecated
	 */
	public function setRendererUrl( $URL ) {
		trigger_error( "The FirePHP::setRendererUrl() method is no longer supported", E_USER_DEPRECATED );
	}
}

class FB
{
	/**
	 * Enable and disable logging to Firebug
	 * @see FirePHP->setEnabled()
	 *
	 * @param boolean $Enabled TRUE to enable, FALSE to disable
	 *
	 * @return void
	 */
	public static function setEnabled( $Enabled ) {
		$instance = FirePHP::getInstance( true );
		$instance->setEnabled( $Enabled );
	}

	/**
	 * Check if logging is enabled
	 * @see FirePHP->getEnabled()
	 * @return boolean TRUE if enabled
	 */
	public static function getEnabled() {
		$instance = FirePHP::getInstance( true );
		return $instance->getEnabled();
	}

	/**
	 * Specify a filter to be used when encoding an object
	 * Filters are used to exclude object members.
	 * @see FirePHP->setObjectFilter()
	 *
	 * @param string $Class  The class name of the object
	 * @param array  $Filter An array or members to exclude
	 *
	 * @return void
	 */
	public static function setObjectFilter( $Class, $Filter ) {
		$instance = FirePHP::getInstance( true );
		$instance->setObjectFilter( $Class, $Filter );
	}

	/**
	 * Set some options for the library
	 * @see FirePHP->setOptions()
	 *
	 * @param array $Options The options to be set
	 *
	 * @return void
	 */
	public static function setOptions( $Options ) {
		$instance = FirePHP::getInstance( true );
		$instance->setOptions( $Options );
	}

	/**
	 * Get options for the library
	 * @see FirePHP->getOptions()
	 * @return array The options
	 */
	public static function getOptions() {
		$instance = FirePHP::getInstance( true );
		return $instance->getOptions();
	}

	/**
	 * Log object to firebug
	 * @see http://www.firephp.org/Wiki/Reference/Fb
	 *
	 * @param mixed $Object
	 *
	 * @return true
	 * @throws Exception
	 */
	public static function send() {
		$instance = FirePHP::getInstance( true );
		$args     = func_get_args();
		return call_user_func_array( array(
										  $instance,
										  'fb' ), $args );
	}

	/**
	 * Start a group for following messages
	 * Options:
	 *   Collapsed: [true|false]
	 *   Color:     [#RRGGBB|ColorName]
	 *
	 * @param string $Name
	 * @param array  $Options OPTIONAL Instructions on how to log the group
	 *
	 * @return true
	 */
	public static function group( $Name, $Options = null ) {
		$instance = FirePHP::getInstance( true );
		return $instance->group( $Name, $Options );
	}

	/**
	 * Ends a group you have started before
	 * @return true
	 * @throws Exception
	 */
	public static function groupEnd() {
		return self::send( null, null, FirePHP::GROUP_END );
	}

	/**
	 * Log object with label to firebug console
	 * @see FirePHP::LOG
	 *
	 * @param mixes  $Object
	 * @param string $Label
	 *
	 * @return true
	 * @throws Exception
	 */
	public static function log( $Object, $Label = null ) {
		return self::send( $Object, $Label, FirePHP::LOG );
	}

	/**
	 * Log object with label to firebug console
	 * @see FirePHP::INFO
	 *
	 * @param mixes  $Object
	 * @param string $Label
	 *
	 * @return true
	 * @throws Exception
	 */
	public static function info( $Object, $Label = null ) {
		return self::send( $Object, $Label, FirePHP::INFO );
	}

	/**
	 * Log object with label to firebug console
	 * @see FirePHP::WARN
	 *
	 * @param mixes  $Object
	 * @param string $Label
	 *
	 * @return true
	 * @throws Exception
	 */
	public static function warn( $Object, $Label = null ) {
		return self::send( $Object, $Label, FirePHP::WARN );
	}

	/**
	 * Log object with label to firebug console
	 * @see FirePHP::ERROR
	 *
	 * @param mixes  $Object
	 * @param string $Label
	 *
	 * @return true
	 * @throws Exception
	 */
	public static function error( $Object, $Label = null ) {
		return self::send( $Object, $Label, FirePHP::ERROR );
	}

	/**
	 * Dumps key and variable to firebug server panel
	 * @see FirePHP::DUMP
	 *
	 * @param string $Key
	 * @param mixed  $Variable
	 *
	 * @return true
	 * @throws Exception
	 */
	public static function dump( $Key, $Variable ) {
		return self::send( $Variable, $Key, FirePHP::DUMP );
	}

	/**
	 * Log a trace in the firebug console
	 * @see FirePHP::TRACE
	 *
	 * @param string $Label
	 *
	 * @return true
	 * @throws Exception
	 */
	public static function trace( $Label ) {
		return self::send( $Label, FirePHP::TRACE );
	}

	/**
	 * Log a table in the firebug console
	 * @see FirePHP::TABLE
	 *
	 * @param string $Label
	 * @param string $Table
	 *
	 * @return true
	 * @throws Exception
	 */
	public static function table( $Label, $Table ) {
		return self::send( $Table, $Label, FirePHP::TABLE );
	}
}


/**
 *
 * @see http://code.google.com/p/php-console
 * @author Barbushin Sergey http://linkedin.com/in/barbushin
 * @version 1.1
 *
 * @desc Sending messages to Google Chrome console
 *
 * You need to install Google Chrome extension:
 * https://chrome.google.com/extensions/detail/nfhmhhlpfleoednkpnnnkolmclajemef
 *
 * All class properties and methods are static because it's required to let
 * them work on script shutdown when FATAL error occurs.
 *
 */
class PhpConsole {

	public static $ignoreRepeatedEvents = false;
	public static $callOldErrorHandler = true;
	public static $callOldExceptionsHandler = true;

	/**
	 * @var PhpConsole
	 */
	protected static $instance;

	protected $handledMessagesHashes = array();
	protected $sourceBasePath;

	protected function __construct($handleErrors, $handleExceptions, $sourceBasePath) {
		if($handleErrors) {
			$this->initErrorsHandler();
		}
		if($handleExceptions) {
			$this->initExceptionsHandler();
		}
		if($sourceBasePath) {
			$this->sourceBasePath = realpath($sourceBasePath);
		}
		$this->initClient();
	}

	public static function start($handleErrors = true, $handleExceptions = true, $sourceBasePath = null) {
		if(!self::$instance) {
			self::$instance = new PhpConsole($handleErrors, $handleExceptions, $sourceBasePath);
		}
	}

	protected function handle(PhpConsoleEvent $event) {
		if(self::$ignoreRepeatedEvents) {
			$eventHash = md5($event->message . $event->file . $event->line);
			if(in_array($eventHash, $this->handledMessagesHashes)) {
				return;
			}
			else {
				$this->handledMessagesHashes[] = $eventHash;
			}
		}
		$this->sendEventToClient($event);
	}

	public function __destruct() {
		self::flushMessagesBuffer();
	}

	/***************************************************************
	CLIENT
	 **************************************************************/

	const clientProtocolCookie = 'phpcslc';
	const serverProtocolCookie = 'phpcsls';
	const serverProtocol = 4;
	const messagesCookiePrefix = 'phpcsl_';
	const cookiesLimit = 50;
	const cookieSizeLimit = 4000;
	const messageLengthLimit = 2500;

	protected static $isEnabledOnClient;
	protected static $isDisabled;
	protected static $messagesBuffer = array();
	protected static $bufferLength = 0;
	protected static $messagesSent = 0;
	protected static $cookiesSent = 0;
	protected static $index = 0;

	protected function initClient() {
		if(self::$isEnabledOnClient === null) {
			self::setEnabledOnServer();
			self::$isEnabledOnClient = self::isEnabledOnClient();
			if(self::$isEnabledOnClient) {
				ob_start();
			}
		}
	}

	protected static function isEnabledOnClient() {
		return isset($_COOKIE[self::clientProtocolCookie]) && $_COOKIE[self::clientProtocolCookie] == self::serverProtocol;
	}

	protected static function setEnabledOnServer() {
		if(!isset($_COOKIE[self::serverProtocolCookie]) || $_COOKIE[self::serverProtocolCookie] != self::serverProtocol) {
			self::setCookie(self::serverProtocolCookie, self::serverProtocol);
		}
	}

	protected function sendEventToClient(PhpConsoleEvent $event) {
		if(!self::$isEnabledOnClient || self::$isDisabled) {
			return;
		}
		$message = array();
		$message['type'] = strpos($event->tags, 'error,') === 0 ? 'error' : 'debug';
		$message['subject'] = $event->type;
		$message['text'] = substr($event->message, 0, self::messageLengthLimit);

		if($event->file) {
			$message['source'] = ($this->sourceBasePath ? preg_replace('!^' . preg_quote($this->sourceBasePath, '!') . '!', '', $event->file) : $event->file) . ($event->line ? ':' . $event->line : '');
		}
		if($event->trace) {
			$traceArray = $this->convertTraceToArray($event->trace, $event->file, $event->line);
			if($traceArray) {
				$message['trace'] = $traceArray;
			}
		}

		self::pushMessageToBuffer($message);

		if(strpos($event->tags, ',fatal')) {
			self::flushMessagesBuffer();
		}
	}

	protected function convertTraceToArray($traceData, $eventFile = null, $eventLine = null) {
		$trace = array();
		foreach($traceData as $call) {
			if((isset($call['class']) && $call['class'] == __CLASS__) || (!$trace && isset($call['file']) && $call['file'] == $eventFile && $call['line'] == $eventLine)) {
				$trace = array();
				continue;
			}
			$args = array();
			if(isset($call['args'])) {
				foreach($call['args'] as $arg) {
					if(is_object($arg)) {
						$args[] = get_class($arg);
					}
					elseif(is_array($arg)) {
						$args[] = 'Array';
					}
					else {
						$arg = var_export($arg, 1);
						$args[] = strlen($arg) > 12 ? substr($arg, 0, 8) . '...\'' : $arg;
					}
				}
			}
			if(isset($call['file']) && $this->sourceBasePath) {
				$call['file'] = preg_replace('!^' . preg_quote($this->sourceBasePath, '!') . '!', '', $call['file']);
			}
			$trace[] = (isset($call['file']) ? ($call['file'] . ':' . $call['line']) : '[internal call]') . ' - ' . (isset($call['class']) ? $call['class'] . $call['type'] : '') . $call['function'] . '(' . implode(', ', $args) . ')';
		}
		$trace = array_reverse($trace);
		foreach($trace as $i => &$call) {
			$call = '#' . ($i + 1) . ' ' . $call;
		}
		return $trace;
	}

	protected static function pushMessageToBuffer($message) {
		$encodedMessageLength = strlen(rawurlencode(json_encode($message)));
		if(self::$bufferLength + $encodedMessageLength > self::cookieSizeLimit) {
			self::flushMessagesBuffer();
		}
		self::$messagesBuffer[] = $message;
		self::$bufferLength += $encodedMessageLength;
	}

	protected static function getNextIndex() {
		return substr(number_format(microtime(1), 3, '', ''), -6) + self::$index++;
	}

	public static function flushMessagesBuffer() {
		if(self::$messagesBuffer) {
			self::sendMessages(self::$messagesBuffer);
			self::$bufferLength = 0;
			self::$messagesSent += count(self::$messagesBuffer);
			self::$messagesBuffer = array();
			self::$cookiesSent++;
			if(self::$cookiesSent == self::cookiesLimit) {
				self::$isDisabled = true;
				$message = array('type' => 'error', 'subject' => 'PHP CONSOLE', 'text' => 'MESSAGES LIMIT EXCEEDED BECAUSE OF COOKIES STORAGE LIMIT. TOTAL MESSAGES SENT: ' . self::$messagesSent, 'source' => __FILE__, 'notify' => 3);
				self::sendMessages(array($message));
			}
		}
	}

	protected static function setCookie($name, $value) {
		if(headers_sent($file, $line)) {
			throw new Exception('PHPConsole:setcookie()执行失败，以有输出产生，导致headers被发出 (' . $file . ':' . $line . '). 请尝试ob_start()进行输出缓冲控制');
		}
		setcookie($name, $value, null, '/');
	}

	protected static function sendMessages($messages) {
		self::setCookie(self::messagesCookiePrefix . self::getNextIndex(), json_encode($messages));
	}

	/***************************************************************
	ERRORS
	 **************************************************************/

	protected $codesTags = array(E_ERROR => 'fatal', E_WARNING => 'warning', E_PARSE => 'fatal', E_NOTICE => 'notice', E_CORE_ERROR => 'fatal', E_CORE_WARNING => 'warning', E_COMPILE_ERROR => 'fatal', E_COMPILE_WARNING => 'warning', E_USER_ERROR => 'fatal', E_USER_WARNING => 'warning', E_USER_NOTICE => 'notice', E_STRICT => 'warning');
	protected $codesNames = array(E_ERROR => 'E_ERROR', E_WARNING => 'E_WARNING', E_PARSE => 'E_PARSE', E_NOTICE => 'E_NOTICE', E_CORE_ERROR => 'E_CORE_ERROR', E_CORE_WARNING => 'E_CORE_WARNING', E_COMPILE_ERROR => 'E_COMPILE_ERROR', E_COMPILE_WARNING => 'E_COMPILE_WARNING', E_USER_ERROR => 'E_USER_ERROR', E_USER_WARNING => 'E_USER_WARNING', E_USER_NOTICE => 'E_USER_NOTICE', E_STRICT => 'E_STRICT');
	protected $notCompitableCodes = array('E_RECOVERABLE_ERROR' => 'warning', 'E_DEPRECATED' => 'warning');
	protected $oldErrorHandler;

	protected function initErrorsHandler() {
		ini_set('display_errors', false);
		ini_set('html_errors', false);
		ini_set('ignore_repeated_errors', self::$ignoreRepeatedEvents);
		ini_set('ignore_repeated_source', self::$ignoreRepeatedEvents);

		foreach($this->notCompitableCodes as $code => $tag) {
			if(defined($code)) {
				$this->codesTags[constant($code)] = $tag;
				$this->codesNames[constant($code)] = $code;
			}
		}

		$this->oldErrorHandler = set_error_handler(array($this, 'handleError'));
		register_shutdown_function(array($this, 'checkFatalError'));
	}

	public function checkFatalError() {
		$error = error_get_last();
		if($error) {
			$this->handleError($error['type'], $error['message'], $error['file'], $error['line']);
		}
	}

	public function handleError($code = null, $message = null, $file = null, $line = null) {
		if(error_reporting() == 0) { // if error has been supressed with an @
			return;
		}
		if(!$code) {
			$code = E_USER_ERROR;
		}

		$event = new PhpConsoleEvent();
		$event->tags = 'error,' . (isset($this->codesTags[$code]) ? $this->codesTags[$code] : 'warning');
		$event->message = $message;
		$event->type = isset($this->codesNames[$code]) ? $this->codesNames[$code] : $code;
		$event->file = $file;
		$event->line = $line;
		$event->trace = debug_backtrace();

		$this->handle($event);

		if(self::$callOldErrorHandler && $this->oldErrorHandler) {
			call_user_func_array($this->oldErrorHandler, array($code, $message, $file, $line));
		}
	}

	/***************************************************************
	EXCEPTIONS
	 **************************************************************/

	protected $oldExceptionsHandler;

	protected function initExceptionsHandler() {
		$this->oldExceptionsHandler = set_exception_handler(array($this, 'handleException'));
	}

	public function handleException(Exception $exception) {
		$event = new PhpConsoleEvent();
		$event->message = $exception->getMessage();
		$event->tags = 'error,fatal,exception,' . get_class($exception);
		$event->type = get_class($exception);
		$event->file = $exception->getFile();
		$event->line = $exception->getLine();
		$event->trace = $exception->getTrace();

		$this->handle($event);

		// TODO: check if need to throw
		if(self::$callOldExceptionsHandler && $this->oldExceptionsHandler) {
			call_user_func($this->oldExceptionsHandler, $exception);
		}
	}

	/***************************************************************
	DEBUG
	 **************************************************************/

	public static function debug($message, $tags = 'debug') {
		if(self::$instance) {
			$event = new PhpConsoleEvent();
			$event->message = $message;
			$event->tags = $tags;
			$event->type = $tags;
			self::$instance->handle($event);
		}
	}
}

class PhpConsoleEvent {
	public $message;
	public $type;
	public $tags;
	public $trace;
	public $file;
	public $line;
}

function debug($message, $tags = 'debug') {
	PhpConsole::debug($message, $tags);
}